import subprocess
import pickle
import os.path
import ma.const
import ma.log


class ProcessRunner(object):
    
    def __init__(self, exec_path, user_args_list, user_process_id, pickle_data=None):
        """init the module. Also pickle the data
        """
        
        self.__log = ma.log.get_logger("ma.commons")
        
        self.__data_to_pickle = pickle_data
        self.__user_args = user_args_list
        self.__user_process_id = user_process_id
        
        # fetch constants
        self.__python_exec = ma.const.XmlData.get_filepath_str_data(ma.const.xml_python_exec)
        self.__niceness = ma.const.XmlData.get_int_data(ma.const.xml_child_process_niceness)
        self.__process_retries = ma.const.XmlData.get_int_data(ma.const.xml_processrunner_fail_retries)
        
        self.__exec_cmd = self.__python_exec + ' ' + exec_path
        self.__args = self.__make_args(self.__user_args)
        
        self.__log.debug('ProcessRunner %s init complete', self.__user_process_id)
        
        
    def start_and_end_process(self, expiry=None):
        """This function starts the process and blocks till the process 
        completes or expires. This function returns True if the return code
        of the process was 0, else False
        """
		
		# keep iterating until all retries exhausted or succefully executed
        while self.__process_retries > 0:
            try:
                # TODO: Could shift to poll() if want to expire the process on a 
                #certain time expiry
                
                # start the process at a certain niceness level
                proc = subprocess.Popen('nice -n ' + str(self.__niceness) + ' ' 
                                + self.__exec_cmd + ' ' + self.__args, shell=True, 
                                stdin=subprocess.PIPE,close_fds=True)
                
                pid = str(proc.pid)
                self.__log.info('Child process %s running: %s', self.__user_process_id, pid)
                
                # Transfer data if expected by the child process
                if self.__data_to_pickle != None:
                    self.__log.info('Child process %s communicating: %s', self.__user_process_id, pid)
                    pickle_str = pickle.dumps([ self.__data_to_pickle ])
                    proc.communicate(pickle_str)
                
                # wait for process to complete
                proc_return = proc.wait()
                if proc_return == 0:
                    self.__log.info('Child process %s - pid %s ended in success', self.__user_process_id, pid)
                    return True
                else:
                    self.__log.error('Child process %s - pid %s ended in failure with this return code %d', self.__user_process_id, pid, proc_return)
                    return False
            
                self.__process_retries = 0
            except Exception as err_msg:
                self.__process_retries -= 1
                self.__log.error('Error occurred while running child process %s: %s .... Retries remaining %d', self.__user_process_id, err_msg, self.__process_retries)
        
        # if loop ended without returning true, it means that retries exhausted
		return False
    
    
    def __make_args(self, args_list):
        """This function makes an arguments list from the group of arguments
        passed
        """
        
        args_list_str = ""
        for arg in args_list:
            args_list_str += str(arg) + " "
            
        return args_list_str
        
    
    @staticmethod
    def store_pickled_data(transfer_list, proc_temp_filename, job_id):
        """This function is used in place of communicate to transfer data to 
        another process in form of a pickled list in a file. To read and use
        the list it will be unpickled and accessed through  __getitem__(idx)
        """
        log = ma.log.get_logger("ma.codes")
        
        try:    
            # pickle the list in the arguments 
            pickle_list = pickle.dumps(transfer_list)
            
            # get the temp filepath
            proc_temp_dest_dir = ma.const.JobsXmlData.get_filepath_str_data(ma.const.xml_local_compute_temp_dir, job_id)
            proc_temp_filepath = os.path.join(proc_temp_dest_dir, proc_temp_filename)
            
            # write the pickled data to the file
            fd = open(proc_temp_filepath, "w+")
            fd.write(pickle_list)
            fd.close()
            
        except Exception as err_msg:
            log.error('Error occurred while pickling data and writing to file %s: %s', proc_temp_filename, err_msg)
    
    
    @staticmethod
    def pull_pickled_data(proc_temp_filename, job_id):
        """This function is used in place of communicate to transfer data from
        a process in form of a list inside a file. To read the returned 
        unpickled list it will be accessed through .__getitem__(idx). Note that
        this function deletes the file it unpickles from
        """
        log = ma.log.get_logger("ma.codes")
        
        try:
            # get the temp filepath
            proc_temp_dest_dir = ma.const.JobsXmlData.get_filepath_str_data(ma.const.xml_local_compute_temp_dir, job_id)
            proc_temp_filepath = os.path.join(proc_temp_dest_dir, proc_temp_filename)
            
            # read the pickled data from the file
            fd = open(proc_temp_filepath, "r")
            pickled_str = fd.read()
            fd.close
            
            # delete the processing file
            os.remove(proc_temp_filepath)
            
            # unpickle data and return list
            transfer_list = pickle.loads(pickled_str)
            return transfer_list
            
        except Exception as err_msg:
            log.error('Error occurred while pickling data and writing to file %s: %s', proc_temp_filename, err_msg)
            